package biolabs.model.abscretion

import biolabs.space.Compartment
import biolabs.space.Compartmentalized
import biolabs.space.ContinuousPosition
import biolabs.space.DiscretePosition
import biolabs.space.Grid
import biolabs.space.SpaceManagingContainer

import scala.collection.JavaConversions._

trait SubstanceHolder[
	P <: DiscretePosition[P, C],
	C <: ContinuousPosition[C, P]
] extends SpaceManagingContainer[P] with Compartmentalized {

	protected val concentrations: java.util.Map[Any, Grid[java.lang.Double, P, C]]

	def getConcentrationMask(): Grid[java.lang.Boolean, P, C]
	def getDimensions() = getConcentrationMask.getSize()
	def getSubstances() = concentrations.keySet()
	def getConcentration(s: Any) = concentrations.get(s)
	def getConcentrationsAt(pos: P): Map[Any, Double] =
		concentrations.map { case (sub, conc) => (sub, conc.at(pos).doubleValue()) }.toMap
	def setConcentrationsAt(pos: P, conc: Map[Any, Double]) = {
		for((s, c) <- conc) {
			concentrations(s).setField(pos, c)
		}
	}

	override def compartments: Iterable[Compartment] = {
		val mask = getConcentrationMask
		val dim = mask.getSize
		val d = dim.getDimension
		return new Iterable[Compartment]() {
			override def iterator: Iterator[Compartment] = return new Iterator[Compartment]() {
				val pos = new Array[Int](d)
				var p: P = dim.newInstance(pos)

				override def hasNext: Boolean = seekNext()

				override def next: Compartment = {
					if(!seekNext())
						throw new IllegalStateException("no more elements")
					incPos()
					return new PositionCompartment(p)
				}

				private def seekNext(): Boolean = {
					while(pos(0) < dim.getCoordinate(0).intValue) {
						if(mask.at(p).booleanValue) {
							return true
						} else {
							incPos()
						}
					}
					return false
				}

				private def incPos() {
					_incPos()
					p = dim.newInstance(pos)
				}

				private def _incPos() {
					val n = pos.length
					for(i <- n-1 to 1 by -1) {
						pos(i) += 1
						if(pos(i) == dim.getCoordinate(i)) {
							pos(i) = 0
						} else {
							return
						}
					}
					pos(0) += 1
				}
			}
		}
	}

	private class PositionCompartment(pos: P) extends Compartment {
		override def getSpeciesAmount(substance: Any) = concentrations(substance).at(pos).doubleValue
		override def getSpeciesAmounts() = getConcentrationsAt(pos)
		override def setSpeciesAmount(species: Any, amount: Double) = concentrations(species).at(pos)
		override def setSpeciesAmounts(amounts: Map[Any, Double]) = setConcentrationsAt(pos, amounts)
		override def toString: String = "Compartment " + pos
	}

	override def handleCapture(captured: java.util.Collection[P]) = {
		for(p <- captured)
			redistributeContent(p)
	}

	/**
	 * Redistributes the substances at the given position to its free neighbor
	 * positions. If there are no free neighbor positions, does nothing.
	 */
	private def redistributeContent(pos: P): Unit = {
		// collect the free neighbors
		val neigh = pos.getNeighbors()
		val i = neigh.listIterator(neigh.size())
		val mask = getConcentrationMask
		while(i.hasPrevious()) {
			// proceeding backward to minimize memory copying on removals,
			// because we are actually working with ArrayList

			val p = i.previous();
			if(!mask.inRange(p) || !mask.at(p).booleanValue())
				i.remove();
		}

		val n = neigh.size();
		if(n==0)
			return

		// redistribute each substance
		// TODO could be done concurrently for all substances,
		// but then probably the overhead would exceed the gain.
		// Maybe having one RecursiveAction for say 10 substances
		// would be beneficial?
		for(conc <- concentrations.values()) {
			val c = conc.setField(pos, 0.0).doubleValue()
			val amount = c/n
			for(np <- neigh) {
				conc.setField(np, conc.at(np).doubleValue() + amount);
			}
		}
	}
}